package com.bootdo.staticmask.core.mask;


import com.bootdo.staticmask.common.ParameterUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.util.DigestUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.bootdo.staticmask.common.ParameterUtil.SALT;

/**
 * 此类实现多种脱敏方法，主要有：加密（MD5）；掩藏；替换；置空；重排；地址截断；手机号摘要；日期偏移和取整
 *
 * @author houy
 */

public class MaskMethod {


    /**
     * 为MaskMethod类创建日志，保存异常处理信息
     */
    private static Logger logger = Logger.getLogger(MaskMethod.class);

    /**
     * 常用名字的汉字库，用来替换姓名
     */
    private static String surname = "怀适耘瀚怜思怡倩逸怿健恨恩聪火恬灵悌炎悠悦邦炫育傲胜惜烟惠惬能愉意焕焱然儿"
            + "允元先光克慕煜慧全慨兰兴典腾醉熙军冠冬冰冷采懋凌野燎燕凝凡凤凯致懿成舒刚初舞舟利航爱爽才扬良艳艺承芊"
            + "芙力芝功芬芮芳芷芸芹勇勋苑勤若英茂范茉化茗茜献振茵千卉半华卓荔南博荣荫荷捷卿玄玉莉莎玑厚莞玟环玲莲莹"
            + "莺菀菁又菊珊友双珍发珠菡叡古可菱菲叶珺琇合吉萌同萍名向君琛琦萦萧琨琪含琬听启琰萱琲琳琴琼瑗葛瑜瑞葳瑶"
            + "瑾璇和璎咏璐璞璧钧瓃蓄铄蓉蓓蓝铭哲锋锐甘蔚甜生锦唱蔼政畅蕊敏蕙教蕤敬蕴畴蕾长善喆薇文斌斯新方旋藏嗣旭问"
            + "痴时旷昂昆嘉昊昌明易昕星映春昭昱阳晋晏晓晖晗晟晨景晴晶陶虹智白暄皓暖暮隽雁雄雅盈盛曜曦雨雪园雰曲固曼盼国"
            + "霁霄震月眉有朋朗望霜霞真圣木未霭露杉材青靖静坚来杰蝶松睿融林枝枫城柏柔知韦音柳石韵韶栋树桂桃行桐项顺梁梅"
            + "梓颖颜梦碧梧墨磊风裕飘飙飞士磬壮复夏夜天央奇奕楠祥奥祯祺好如榆妍福榕香妙妞馥馨妮妹禹秀觅秋槐姝姣姬姿言娅"
            + "娇娜娟娣娥娴穆婉婕驰婵婷驹穹空骄骊骏媛骞立高童端竹笑欢欣魄歌子策存孟筠正季孤学武宁宇安宏宛宜实宣家宸容宾"
            + "访寄毅富寒毓诗诚语寻小民尔气尘谦谨水谷永尹汉江池豪豫山岑岚沛岩河泓波峰泰峻泽洁贞素贤紫贵洽流赋济赞浩赫海"
            + "起超涉涛润涵淑巧淳一布清渊希专世东丝鸣鸥温中丰丹丽鸿乃湃义之鹏乐湘湛书平年幻幼广庆源云亘亚红亦纨亨溪纬亮纯"
            + "纳康人仁绍从经仕仙黛滢代令以仪绮维建绿开伊弘优会伟伦漪伯强弼齐佑彤彦彩轩彪彬彭轮影佳辉美澎龙澜依辞侠群辰德" +
            "羽达心忆俊翎迎运翔志进远保翠信濮修翰念忻";


    public MaskMethod() {

    }


    /**
     * 实现MD5加密操作
     *
     * @param strdata ,需处理的数据字段
     * @param param1  ,开始处理的位置
     * @param param2  ,处理的总位数
     * @return String类型, MD5处理的结果，异常情况时返回原值
     */
    public String MD5Encrypt(String strdata, String param1, String param2) {
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return "";
        } else {
            strdata = strdata + SALT;
            return DigestUtils.md5DigestAsHex(strdata.getBytes());
        }
    }


    /**
     * 实现隐藏操作，包括对中文，邮箱，其他情况的处理
     *
     * @param strdata ,需处理的数据字段
     * @param param1  ,开始处理的位置
     * @param param2  ,处理的总位数
     * @return String类型, 隐藏处理的结果
     */
    public String hideMask(String strdata, String param1, String param2) {
        // 判断数据是否为空
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return "";
        }
        // 转换为字符串中的下标从0开始
        int begin = Integer.valueOf(param1) - 1;
        int size = Integer.valueOf(param2);
        int length = StringUtils.length(strdata);

        // 初始化最终返回的结果
        String str;

        if (begin > length) {
            str = strdata;
        } else if (size >= length - begin && begin <= length) {
            str = StringUtils.rightPad(StringUtils.left(strdata, begin),
                    length, "*");
        } else {
            // rightPad在param1之后链接多个param3，保证总长为param2
            str = StringUtils.rightPad(StringUtils.left(strdata, begin),
                    begin + size, "*").concat(
                    StringUtils.right(strdata, length - begin - size));
        }
        return str;
    }

    /**
     * 将敏感字段进行置空处理
     *
     * @param msg    ,需处理的数据字段
     * @param param1 ,开始处理的位置
     * @param param2 ,处理的总位数
     * @return String类型, 置空处理的结果
     */
    public String NULLCutMask(String msg, String param1, String param2) {
        if (StringUtils.isBlank(msg) || msg.equals("null")) {
            return "";
        }

        int begin = Integer.valueOf(param1) - 1;
        int size = Integer.valueOf(param2);

        int length = msg.length();

        if (begin > length) {
            return msg;
        } else if (size >= length - begin && begin <= length) {
            return msg.substring(0, begin);
        } else {
            return msg.substring(0, begin).concat(
                    msg.substring(begin + size, length));
        }
    }

    /**
     * 实现数据字段的替换操作
     *
     * @param strdata 需处理的数据字段
     * @param param1  开始处理的位置
     * @param param2  处理的总位数
     * @return String类型, 替换处理的结果
     */
    public String replaceMask(String strdata, String param1, String param2) {
        String str = "";
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return "";
        }
        // 转换为字符串中的下标0---
        int begin = Integer.valueOf(param1) - 1;
        int size = Integer.valueOf(param2);

        int length = strdata.length();

        if (isChinese(strdata) && length < 5) {
            // 判断是否为中文名字
            if (begin > length) {
                str = strdata;
            } else if (size >= length - begin && begin <= length) {
                str = StringUtils.left(strdata, begin)
                        .concat(getReplacename(strdata.substring(begin), length
                                - begin));
            } else {
                String str1 = strdata.substring(begin, begin + size);
                str = StringUtils
                        .left(strdata, begin)
                        .concat(getReplacename(str1, size))
                        .concat(StringUtils.right(strdata, length - begin
                                - size));
            }
        } else {
            // 其他情况
            if (begin > length) {
                str = strdata;
            } else if (size >= length - begin && begin <= length) {
                str = StringUtils.left(strdata, begin).concat(
                        getReplaceString(strdata.substring(begin), length
                                - begin));
            } else {
                String str1 = strdata.substring(begin, begin + size);
                str = StringUtils
                        .left(strdata, begin)
                        .concat(getReplaceString(str1, size))
                        .concat(StringUtils.right(strdata, length - begin
                                - size));
            }
        }
        return str;
    }

    /**
     * 实现对数据字段进行重排操作
     *
     * @param strdata 需处理的数据字段
     * @param param1  处理开始的位置
     * @param param2  处理的总位数
     * @return String类型的处理结果
     */
    public String shuffleMask(String strdata, String param1, String param2) {
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return "";
        }
        // 转换为字符串中的下标0---
        int begin = Integer.valueOf(param1) - 1;
        int size = Integer.valueOf(param2);
        int length = strdata.length();

        String str;

        if (begin > length) {
            str = strdata;
        } else if (size >= length - begin && begin <= length) {
            str = StringUtils.left(strdata, begin).concat(
                    getShuffleString(strdata.substring(begin)));
        } else {
            String str1 = strdata.substring(begin, begin + size);
            str = StringUtils
                    .left(strdata, begin)
                    .concat(getShuffleString(str1))
                    .concat(StringUtils.right(strdata, length - begin
                            - size));
        }
        return str;
    }

    /**
     * 手机号码保留地域信息，其他位进行摘要处理
     *
     * @param strdata 需处理的数据字段
     * @param param1  开始处理的位置
     * @param param2  处理的总位数
     * @return String类型的处理结果
     */
    public String phoneMask(String strdata, String param1, String param2) {
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return "";
        }
        // 只处理后4位
        if (strdata.length() < 4) {
            return strdata;
        }
        String str_first = strdata.substring(0, strdata.length() - 4);
        String str_two = strdata.substring(strdata.length() - 4, strdata.length());

        //返回脱敏后电话号码值
        String newStr = ParameterUtil.PHONECODE.get(Integer.parseInt(str_two));
        return str_first.concat(newStr);
    }

    /**
     * 对日期数据进行缩减变换，随机产生偏移量
     *
     * @param strdata 需处理的日期数据
     * @param param1  开始处理的位置
     * @param param2  处理的总位数
     * @return String类型的处理结果
     */
    public String dateConversion(String strdata, String param1, String param2) {
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return null;
        }
        // 根据输入的格式，进行字符串到日期的转换，建议后期统一格式，这样不论如何的输入，保证输出格式一致
        SimpleDateFormat sdf = stringtoDate(strdata);
        Random rm = new Random();
        StringBuilder str = new StringBuilder("");
        try {
            Date date = sdf.parse(strdata);
            Calendar c = Calendar.getInstance();
            c.setTime(date);
            c.add(Calendar.DAY_OF_YEAR, rm.nextInt(30) + 1);
            date = c.getTime();
            str.append(sdf.format(date));
        } catch (ParseException e) {
            logger.error(e);
            return str.append(strdata).toString();
        }
        return str.toString();

    }

    /**
     * 对日期数据进行取整操作，可实现对分秒的取整
     *
     * @param strdata 需处理的日期数据
     * @param param1  开始处理的位置
     * @param param2  处理的总位数
     * @return String类型的处理结果
     */
    public String dateInteger(String strdata, String param1, String param2) {
        if (StringUtils.isBlank(strdata) || strdata.equals("null")) {
            return null;
        }
        // 对分和秒进行取整
        SimpleDateFormat sdf = stringtoDate(strdata);
        StringBuilder str = new StringBuilder("");

        try {
            Date date = sdf.parse(strdata);
            Calendar c = Calendar.getInstance();
            c.setTime(date);
            c.set(Calendar.SECOND, 0);
            c.set(Calendar.MINUTE, 0);
            date = c.getTime();
            str.append(sdf.format(date));
        } catch (ParseException e) {
            logger.error(e);
            return str.append(strdata).toString();
        }
        return str.toString();
    }

    /**
     * 对地址数据字段进行截断操作，根据地址的分类分级大小进行有效的截断
     *
     * @param msg  需处理的地址数据
     * @param key1 大的地址类级
     * @param key2 小的地址类级
     * @return String类型的处理结果
     */
    public String addressMask(String msg, String key1, String key2) {
        // 数据字段为空，返回""值
        if (StringUtils.isBlank(msg) || msg.equals("null")) {
            return "";
        }
        // 输入两个类级参数均为空
        if (StringUtils.isBlank(key1) && StringUtils.isBlank(key2)) {
            return msg;
        }
        // 定义字符串数组rule，表示地址的分类分级情况，由大到小
        String[] rule = {"省", "市", "区", "县", "镇", "乡", "村", "路", "号", "元", "间"};

        int index1 = msg.indexOf(key1);
        int index2 = msg.indexOf(key2);
        // 输入两个类级大小相反，返回原始数据
        if (index1 > index2 && index2 > 0) {
            return msg;
        }

        String str = "";
        // 对两个参数进行分析，每个值有3种情况：存在，不存在于数据段，空
        if (index1 < 0 && index2 < 0) {
            str = StringUtils.join(new String[]{"**" + key1, "**" + key2});
        } else if (index1 <= 0 && index2 > 0) {
            if (index1 == 0) {
                str = msg.substring(0, index2 + 1);
            } else {
                index1 = searchIndexOf1(msg, rule, key1);
                str = msg.substring(index1 + 1, index2 + 1);
            }
        } else if (index1 > 0 && index2 <= 0) {
            index1 = searchIndexOf1(msg, rule, key1);
            if (index2 == 0) {
                str = msg.substring(index1 + 1);
            } else {
                index2 = searchIndexOf2(msg, rule, key2);
                str = msg.substring(index1 + 1, index2 + 1);
            }
        } else if (index1 < 0 && index2 == 0) {
            index1 = searchIndexOf1(msg, rule, key1);
            str = msg.substring(index1 + 1);
        } else if (index1 == 0 && index2 < 0) {
            index2 = searchIndexOf2(msg, rule, key2);
            str = msg.substring(0, index2 + 1);
        } else {
            index1 = searchIndexOf1(msg, rule, key1);
            str = msg.substring(index1 + 1, index2 + 1);

        }
        return str;
    }

    /**
     * 寻找小地址类级在地址数据中的位置，地址数据中没有该类级，则返回比key较大的、接近的类级在地址数据中的位置
     *
     * @param msg  需处理的地址数据
     * @param rule 地址字符串数组
     * @param key  小地址类级
     * @return int型的索引值
     */
    private int searchIndexOf2(String msg, String[] rule, String key) {
        // 关键字在msg中的位置
        int index = msg.indexOf(key);
        // 关键字在rule中的位置
        int k = findKey(rule, key);

        // 向前(大方向)搜索
        for (int i = k; i > 0; i--) {
            index = msg.indexOf(rule[i]);
            if (index > 0) {
                break;
            }
        }
        return index;
    }

    /**
     * 寻找大地址类级在地址数据中的位置，地址数据中没有该类级，则返回比key较小的、接近的类级在地址数据中的位置
     *
     * @param msg  需处理的地址数据
     * @param rule 地址字符串数组
     * @param key  大地址类级
     * @return int型的索引值
     */
    private int searchIndexOf1(String msg, String[] rule, String key) {
        // 关键字在msg中的位置
        int index = msg.indexOf(key);
        // 关键字在rule中的位置
        int k = findKey(rule, key);
        int index0 = 0;

        // 向后搜索
        if (k == 0) {
            index0 = -1;
        } else {
            for (int i = k; i <= rule.length && index <= 0; i++) {
                index = msg.indexOf(rule[i]);
                k = i;
            }
            index0 = msg.indexOf(rule[k - 1]);
            for (int i = k - 1; i >= 0 && index0 <= 0; i--) {
                index0 = msg.indexOf(rule[i]);
            }
        }
        return index0;
    }

    /**
     * 寻找给定关键字在字符串数组中的位置
     *
     * @param rule 地址类级的字符串数组
     * @param key  给定的地址类级
     * @return 返回关键字的位置
     */
    private int findKey(String[] rule, String key) {
        int k = -1;
        for (int i = 0; i < rule.length; i++) {
            if (key.equals(rule[i])) {
                k = i;
            }
        }
        return k;
    }

    /**
     * 使用UnicodeScript方法判断某个字符是否为汉字
     *
     * @param c 需判断的字符
     * @return
     */
    private boolean isChinese(char c) {
        Character.UnicodeScript sc = Character.UnicodeScript.of(c);
        if (sc == Character.UnicodeScript.HAN) {
            return true;
        }
        return false;
    }

    /**
     * 判断数据字段是否为汉字
     *
     * @param strName 需判断的数据字段
     * @return boolean量
     */
    private boolean isChinese(String strName) {
        char[] ch = strName.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            char c = ch[i];
            if (isChinese(c)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 产生随机的汉字， 范围在0x4E00----0x9FA5
     *
     * @return char类型数据
     */
    private char getRandomChar(char c) {
        Random ran = new Random();
        int delta = 0x9FA5 - 0x4E00 + 1;
        return (char) (0x4E00 + ran.nextInt(delta));

    }

    /**
     * 对字符串进行重排操作
     *
     * @param strdata 处理的原数据
     * @return String 重排的数据
     */
    private String getShuffleString(String strdata) {
        List list = new ArrayList();
        for (int i = 0; i < strdata.length(); i++) {
            list.add(i, strdata.charAt(i));
        }
        Collections.shuffle(list);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
        }
        return sb.toString();
    }

    /**
     * 产生替换操作中随机的字母或数字操作
     *
     * @param strdata 处理的原数据
     * @param length  处理的位数
     * @return String 产生长度一致的随机字母或数字
     */
    private String getReplaceString(String strdata, int length) {
        StringBuilder str = new StringBuilder("");
        Random r = new Random();
        for (int i = 0; i < length; i++) {
            if (isChinese(strdata.charAt(i))) {
                str.append(getRandomChar(strdata.charAt(i)));
            } else if (Character.isLetter(strdata.charAt(i))) {
                str.append((char) (Math.random() * 26 + 97));
            } else if (Character.isDigit(strdata.charAt(i))) {
                str.append(r.nextInt(10));
            } else {
                str.append(strdata.charAt(i));
            }
        }
        return str.toString();
    }

    /**
     * 得到随机替换的名字，常用汉字
     *
     * @param substring
     * @param length
     * @return
     */
    private String getReplacename(String substring, int length) {
        StringBuilder str = new StringBuilder("");
        Random r = new Random();
        for (int i = 0; i < length; i++) {
            str.append(surname.charAt(r.nextInt(surname.length())));

        }
        return str.toString();
    }

    /**
     * 根据输入的格式，进行字符串到日期的转换
     *
     * @param strdata 字符串型的日期数据
     * @return SimpleDateFormat 与原数据一致的日期格式
     */
    private SimpleDateFormat stringtoDate(String strdata) {
        SimpleDateFormat format = null;
        if (strdata.contains("-")) {
            format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        } else if (strdata.contains("/")) {
            format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        }
        return format;
    }
}
